package com.example.his.api.mis.impl;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.example.his.api.common.MinioUtil;
import com.example.his.api.common.PageUtils;
import com.example.his.api.db.dao.GoodsDao;
import com.example.his.api.db.pojo.GoodsEntity;
import com.example.his.api.exception.HisException;
import com.example.his.api.mis.service.GoodsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.json.JSONObject;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;


@Service ( "MisGoodsServiceImpl" )
@Slf4j
public class GoodsServiceImpl implements GoodsService {

  // 获取dao
  @Resource
  private GoodsDao goodsDao;

  @Resource
  private MinioUtil minioUtil;

  /**
   * 分页查询商品列表
   *
   * @param param 查询参数，包含分页信息(page:当前页码, length:每页记录数)以及其他查询条件
   * @return PageUtils 分页结果对象，包含商品列表和分页信息
   */
  @Override
  public PageUtils searchByPage( Map param ) {
    // 初始化商品列表
    ArrayList < HashMap > list = new ArrayList <>();

    // 查询符合条件的商品总数
    Long count = goodsDao.searchCount( param );

    // 如果存在符合条件的商品记录，则查询分页数据
    // 如果存在符合条件的商品记录，则查询分页数据
    if ( count > 0 ) {
      list = goodsDao.searchByPage( param );
    }

    // 从参数中获取分页信息
    int page = MapUtil.getInt( param, "page" );
    int length = MapUtil.getInt( param, "length" );

    // 构造分页结果对象
    PageUtils pageUtils = new PageUtils( list, count, page, length );

    return pageUtils;
  }

  /**
   * 上传商品图片到MinIO服务器
   *
   * @param file 要上传的图片文件
   * @return String 上传后的文件访问路径
   */
  @Override
  public String uploadImage( MultipartFile file ) {
    // 生成新的文件名，使用UUID保证唯一性
    String filename = IdUtil.simpleUUID() + ".jpg";
    // 构建文件在MinIO中的存储路径
    String path = "front/goods/" + filename;
    // 调用MinioUtil工具类完成文件上传
    minioUtil.uploadImage( path, file );
    // 返回文件访问路径
    return path;
  }

  /**
   * 新增商品记录
   *
   * @param entity 商品实体对象
   * @return int 受影响的行数
   */
  @Override
  @Transactional
  public int insert( GoodsEntity entity ) {
    // 计算商品新的md5值，用于后续的数据一致性校验
    String md5 = genEntityMd5( entity );
    entity.setMd5( md5 );

    // 调用DAO层插入商品记录
    int rows = goodsDao.insert( entity );

    return rows;
  }

  /**
   * 根据商品ID查询商品详情
   *
   * @param id 商品ID
   * @return 包含商品详情的HashMap，其中tag和checkup字段被转换为JSONArray格式
   */
  @Override
  public HashMap searchById( int id ) {
    // 构造查询参数
    HashMap param = new HashMap() {{
      put( "id", id );
    }};

    // 调用DAO层查询商品详情
    HashMap map = goodsDao.searchById( param );

    // 定义需要转换为JSONArray的字段列表
    String[] columns = { "tag", "checkup_1", "checkup_2", "checkup_3", "checkup_4" };

    // 遍历指定字段，将JSON字符串转换为JSONArray
    for ( String one : columns ) {
      String temp = MapUtil.getStr( map, one );
      if ( temp != null ) {
        JSONArray array = JSONUtil.parseArray( temp );
        map.replace( one, array );
      }
    }

    return map;
  }

  /**
   * 更新商品信息
   *
   * @param entity 商品实体对象，包含更新后的商品信息
   * @return int 受影响的行数，表示更新操作影响的记录数量
   */
  @Override
  @Transactional
  @CacheEvict ( cacheNames = "goods", key = "#entity.id" )
  public int update( GoodsEntity entity ) {
    // 修改商品信息需要重新计算MD5值，用于数据一致性校验
    String md5 = genEntityMd5( entity );
    entity.setMd5( md5 );

    // 调用DAO层执行更新操作
    int rows = goodsDao.update( entity );

    return rows;
  }

  /**
   * 更新商品的检查项信息
   * 该方法会解析上传的Excel文件，提取检查项数据并存储到数据库中
   *
   * @param id   商品ID
   * @param file 包含检查项信息的Excel文件
   * @throws HisException 当文件处理失败或数据更新失败时抛出异常
   */
  @Override
  @Transactional
  @CacheEvict ( cacheNames = "goods", key = "#id" )
  public void updateCheckup( int id, MultipartFile file ) {
    // 创建空的ArrayList用于保存从Excel文档中解析出来的数据
    ArrayList < LinkedHashMap < String, String > > list = new ArrayList <>();

    // 读取Excel文件内容
    try ( InputStream in = file.getInputStream();
          BufferedInputStream bin = new BufferedInputStream( in ) ) {

      XSSFWorkbook workbook = new XSSFWorkbook( bin );
      // 获取Excel文档中第一个Sheet页
      XSSFSheet sheet = workbook.getSheetAt( 0 );

      // 从第二行（第一行是表头）循环到最后一行
      for ( int i = 1; i <= sheet.getLastRowNum(); i++ ) {
        XSSFRow row = sheet.getRow( i );

        // 获取检查地点
        XSSFCell cell_1 = row.getCell( 0 );
        String value_1 = cell_1.getStringCellValue();

        // 获取导诊名称
        XSSFCell cell_2 = row.getCell( 1 );
        String value_2 = cell_2.getStringCellValue();

        // 获取检查项
        XSSFCell cell_3 = row.getCell( 2 );
        String value_3 = cell_3.getStringCellValue();

        // 获取采集方式
        XSSFCell cell_4 = row.getCell( 3 );
        String value_4 = cell_4.getStringCellValue();

        // 获取模板编码
        XSSFCell cell_5 = row.getCell( 4 );
        String value_5 = cell_5.getStringCellValue();

        // 获取性别要求
        XSSFCell cell_6 = row.getCell( 5 );
        String value_6 = cell_6.getStringCellValue();

        // 获取模板值
        XSSFCell cell_7 = row.getCell( 6 );
        String value_7 = cell_7.getStringCellValue();

        // 获取输出模板
        XSSFCell cell_8 = row.getCell( 7 );
        String value_8 = cell_8.getStringCellValue();

        // 希望Map对象按照添加顺序保存键值对
        LinkedHashMap < String, String > map = new LinkedHashMap < String, String >() {{
          put( "place", value_1 );
          put( "name", value_2 );
          put( "item", value_3 );
          put( "type", value_4 );
          put( "code", value_5 );
          put( "sex", value_6 );
          put( "value", value_7 );
          put( "template", value_8 );
        }};

        // 把当前行解析出来的数据保存到ArrayList对象中
        list.add( map );
      }
      // 打印解析的结果，测试完即可注释掉这句话
      System.out.println( list );
    }
    catch ( Exception e ) {
      log.error( "处理Excel文件失败", e );
      throw new HisException( "处理Excel文件失败", e );
    }

    if ( list.size() == 0 ) {
      throw new HisException( "文档内容无效" );
    }

    // 把文件存储到Minio服务器
    String path = "/mis/goods/checkup/" + id + ".xlsx";
    minioUtil.uploadExcel( path, file );

    // 根据商品ID查询商品记录，然后重新计算MD5值，更新商品记录的checkup和md5字段
    GoodsEntity entity = goodsDao.searchEntityById( id );
    String temp = JSONUtil.parseArray( list ).toString();
    entity.setCheckup( temp );
    String md5 = genEntityMd5( entity );

    HashMap < String, Object > param = new HashMap < String, Object >() {{
      put( "id", id );
      put( "checkup", temp );
      put( "md5", md5 ); // 更新MD5字段
    }};

    // 保存数据
    int rows = goodsDao.updateCheckup( param );
    if ( rows != 1 ) {
      throw new HisException( "更新商品信息失败" );
    }
  }

  /**
   * 更新商品状态
   *
   * @param param 包含商品ID和状态值的参数Map
   *              - id: 商品ID
   *              - status: 新的状态值(0-下架, 1-上架)
   * @return boolean 更新成功返回true，失败返回false
   */
  @Override
  @Transactional
  @CacheEvict ( cacheNames = "goods", key = "#param.get('id')",
      condition = "#param.get('status')==false" )
  public boolean updateStatus( Map param ) {
    // 调用DAO层更新商品状态
    int rows = goodsDao.updateStatus( param );
    // 根据影响行数判断更新是否成功
    boolean bool = ( rows == 1 );

    return bool;
  }

  /**
   * 根据商品ID数组批量删除商品记录，并同步删除对应的封面图片
   *
   * @param ids 要删除的商品ID数组
   * @return int 实际删除的记录数
   */
  @Override
  @Transactional
  @CacheEvict ( cacheNames = "goods", key = "#ids" )
  public int deleteByIds( Integer[] ids ) {
    // 删除记录之前先查询商品封面图片路径，用于后续删除文件
    ArrayList < String > list = goodsDao.searchImageByIds( ids );

    // 执行数据库记录删除操作
    int rows = goodsDao.deleteByIds( ids );

    // 如果删除成功（影响行数大于0），则删除对应的封面图片文件
    if ( rows > 0 ) {
      // 遍历所有图片路径，调用Minio工具类删除文件
      list.forEach( ( path ) -> {
        minioUtil.deleteFile( path );
      } );
    }

    return rows;
  }

  /**
   * 生成商品实体的MD5摘要值，用于数据一致性校验
   * 过滤掉不需要参与摘要计算的字段（如自增ID、时间戳等）
   *
   * @param entity 商品实体对象
   * @return String 生成的MD5摘要值（16进制大写）
   */
  public String genEntityMd5( GoodsEntity entity ) {
    // 将商品实体转换为JSON对象
    JSONObject json = new JSONObject( entity );

    // 移除不参与MD5计算的字段
    json.remove( "id" );          // 自增主键
    json.remove( "createTime" );  // 创建时间
    json.remove( "updateTime" );  // 更新时间
    json.remove( "md5" );         // 已有的MD5值
    json.remove( "status" );      // 状态字段
    json.remove( "salesVolume" ); // 销量字段
    json.remove( "partId" );      // 部件ID字段

    // 使用MD5算法生成摘要并转换为大写16进制字符串
    String md5 = MD5.create().digestHex16( json.toString() ).toUpperCase();
    return md5;
  }

}
